package com.korion.channel

import okio.*
import java.lang.Exception
import java.lang.IllegalArgumentException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
import java.net.SocketAddress

class Connection{

    companion object{
        private const val TAG = "Connection"
    }

    constructor(host: String, port: Int){
        mIntelAddress = InetAddress.getByName(host)
        require(port in 1..65535) { "port must be between 1 to 65535" }
        mPort = port
    }

    constructor(socket: Socket){
        mSocket = socket
        val socketAddress = socket.remoteSocketAddress as InetSocketAddress
        mIntelAddress = socketAddress.address
        mPort = socketAddress.port
    }

    private var mIntelAddress: InetAddress? = null
    private var mPort: Int = 0
    private var mSocket: Socket? = null
    private var mSource: BufferedSource? = null
    private var mSink: BufferedSink? = null
    private var mConnected: Boolean = false

    fun isConnected() = mConnected

    fun connect(){
        if (!mConnected){
            doOrClose{
                if (mSocket == null){
                    mSocket = Socket(mIntelAddress, mPort)
                }
                mSocket?.run {
                    mSource = source().buffer()
                    mSink = sink().buffer()
                }
            }
        }
    }

    fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size){
        doOrClose {
            mSink?.run {
                this.write(source, offset, byteCount)
                flush()
            }
        }
    }

    fun writeInt(num: Int){
        doOrClose {
            mSink?.run {
                this.writeInt(num)
                flush()
            }
        }
    }


    fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size): Int{
        try {
            return mSource?.read(sink, offset, byteCount) ?: -1
        }catch (e: Exception){
            close()
            throw e
        }
    }

    fun readInt(): Int{
        try {
            return mSource?.readInt() ?: -1
        }catch (e: Exception){
            close()
            throw e
        }
    }

    fun close(){
        mConnected = false
        mSink?.closeSafely()
        mSource?.closeSafely()
        mSocket.closeSafely()
    }

    private fun doOrClose(block: () -> Unit){
        try {
            block()
        } catch(e: IOException){
            close()
            throw e
        }
    }
}